"""Catchall methods to attempt a list or edit on any class added to the model"""

from django.template import loader, RequestContext
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import get_object_or_404
from django import newforms as forms
from django.utils.translation import ugettext_lazy as _
from duetopia.register.models import Agent
from django.db.models.loading import get_app, get_model, get_models
from django.db import models 
from django.db.models.query import QuerySet
from django.core.paginator import ObjectPaginator, InvalidPage
from django.contrib.admin.views.main import ChangeList
from django.contrib.gis.geos import fromstr
from django.conf import settings
from duetopia.register.geo import box_as_wkt, poly_as_wkt
from mx.DateTime import DateTimeFrom
import datetime

from warnings import warn
import re 

__all__ = ['edit_object', 'list_objects','search_list','search_spatial']

def edit_object(request, app, model, pk=None, template='register/edit_object.html'):
    """Given an integer primary key, will generate edit form for object of class 'model', passed in."""
    form = None
    model_class = get_model(app, model)
    if not model_class._meta.admin:
        raise Http404

    if pk: ## update case
        object = get_object_or_404(model_class, pk=pk)
        Form = forms.form_for_instance(object)
    else:
        Form = forms.form_for_model(model_class)

    if not request.POST:  ## empty form data
        form = Form()
    else: ## validate form data
        form = Form(request.POST)
        try:
            object = form.save()
            redirect_to = settings.WWW_BASEURL+model+'/'
            return HttpResponseRedirect(redirect_to)
        except ValueError:
            pass

    context = {
        'form' : form,
        'model' : model_class._meta,
        'baseurl' : settings.WWW_BASEURL,
    }

    c = RequestContext(request, context)
    t = loader.get_template(template)
    return HttpResponse(t.render(c))

def list_objects(request, app, model, template='register/list_objects.html'):
    """Given a model name (e.g. dataset, layer), return a list of matching objects. Can filter the list according to query parameters - see get_query_set, below."""
    model = get_model(app, model)
    if not model._meta.admin:
        raise Http404

    context = {
        'model' : model._meta,
        'cl' : ChangeList(request, model),
        'baseurl' : settings.WWW_BASEURL,
        'tilecache' : settings.TILECACHE,
    }
    c = RequestContext(request, context)
    t = loader.get_template(template)
    return HttpResponse(t.render(c))

def search_list(request,template='register/search.html'):
    """Search datasets/sources. (See MDSearchList)"""
    model = get_model('register','dataset')
    params = dict(request.GET.items())
    context = {
        'model' : model._meta,
        'cl' : MDSearchList(request, model),
        'baseurl' : settings.WWW_BASEURL,
        'tilecache' : settings.TILECACHE,
        'params': params,
    }
    for k in params.keys():
        context[k] = params[k]
    c = RequestContext(request, context)
    t = loader.get_template(template)
    return HttpResponse(t.render(c))

def search_spatial(request,template='register/spatial_search.html'):
    return search_list(request,template=template)

class MDSearchList(ChangeList):
    """Replaces the default search facilty for Django objects. The ChangeList type handles layout, pagination, etc of lists of results."""

    def get_results(self, request):
        # adapted from ChangeList to run a more complex get_query_set
        
        # a list rather than a query set of in this case DataSets 
        results = self.get_query_results()
        paginator = ObjectPaginator(results, self.lookup_opts.admin.list_per_page)
        # Get the number of objects, with admin filters applied.
        result_count = None
        try:
            result_count = paginator.hits
            
        except:
            raise IncorrectLookupParameters

        # Get the total number of objects, with no admin filters applied.
        full_result_count = len(results)

        MAX_SHOW_ALL_ALLOWED = 200
        can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
        multi_page = result_count > self.lookup_opts.admin.list_per_page

        # Get the list of objects to display on this page.
        if (self.show_all and can_show_all) or not multi_page:
            result_list = results 
        else:
            try:
                result_list = paginator.get_page(self.page_num)
            except InvalidPage:
                result_list = ()

        self.result_count = result_count
        self.full_result_count = full_result_count
        self.result_list = result_list
        self.can_show_all = can_show_all
        self.multi_page = multi_page
        self.paginator = paginator


    def get_query_results(self):
        """A queryset corresponds to a search that is prepared but not yet run. This method hands a QuerySet back to the SearchList's get_results method. 
        If we get a parameter named 'q', try this against various text fields.
        Otherwise restrict search of fields to specific named parameters."""
        from django.contrib.gis.db.models.query import Q, GeoQ

        # list of queries, in case we see ManyToMany relations.
        # see http://code.djangoproject.com/ticket/6432 for reason why.
        queries = []
        q = Q()
        
        lookup = {}
        for p in self.params:
            if self.params[p] != '':
                lookup[p] = self.params[p]
        #lookup = self.params.copy() # a dictionary of the query string
        q = Q() # our main django query for the filter

        for k in lookup.keys():
            # if we see namespaces for query params, but we can lose them.
            if re.match('\w+:',k):
                k2 = k.replace('\w+:','')
                lookup[k2] = lookup.pop(k)
            
        # __ Spatial Extents __
        # filter within extents, implicitly using a GeoQuerySet
        if 'box' in lookup.keys():
            # same format as a GeoRSS box - "west, south, east, north"
            box = lookup['box']
            try:
                ext = box.split(',')
                geos_poly = fromstr(box_as_wkt(ext))    
                q = q | GeoQ(extents__within = geos_poly)
            except:
                pass # fail silently. (blowing up could be better option if possible cleanly.)

        if 'p' in lookup.keys() or 'polygon' in lookup.keys():
            # this is to complete OpenSearch support a bit.
            poly = lookup['p'] | lookup['polygon']
            try:
                ext = poly.split(',')
                geos_poly = fromstr(poly_as_wkt(ext))
                q = q | Q(extents__within = geos_poly)
            except:
                pass

        # __Temporal Extents __
        if 'dtstart' in lookup.keys() or 'dtend' in lookup.keys():
            dtstart = dtend = dtype = None
            # @@ for now rely on typed dates - replace w/ slider UI later?
            if 'dtstart' in lookup.keys():
                try:
                    dtstart = DateTimeFrom(lookup['dtstart'])
                except ValueError, e:
                    return e
            else: dtstart =  datetime.date(1970,1,1)

            if 'dtend' in lookup.keys():
                try:
                    dtend = DateTimeFrom(lookup['dtend'])
                except ValueError, e:
                    return e
            else: dtend = datetime.date.today()
            
            # date range query on only one date field at a time.
            # choosing from published, updated, indexed, extents
            # if none specified, assume is for data itself (extents) 
            if 'dtype' in lookup.keys():
                dtype = lookup['dtype']
            else:
                dtype = 'extents'

            if dtype == 'extents':
                eq = Q(timespan__dtstart__range = (dtstart,dtend),timespan__dtend__range = (dtstart, dtend))
                # a ManyToMany relation, so make an extra query
                queries.append(eq)

            elif dtype == 'published':
                q = q | Q(published__range = (dtstart,dtend))
            elif dtype == 'updated':
                q = q | Q(updated__range = (dtstart,dtend))
            elif dtype == 'indexed':
                q = q | Q(indexed__range = (dtstart,dtend))

        # next 3 are ManyToMany relations, so make an extra query
        if 'keyword' in lookup.keys():
            queries.append(Q(keywords__word__icontains = lookup['keyword']))

        if 'contactname' in lookup.keys():
            queries.append(Q(contacts__name__icontains = lookup['contactname']))
        if 'contactemail' in lookup.keys():
            queries.append(Q(contacts__email__icontains= lookup['contactemail']))

        if 'q' in lookup.keys():
            # rough search, use all text-indexable fields, e.g. those 
            # listed as search_fields by the django model
            fields = self.model._meta.admin.search_fields 
            parts = []
            for f in fields:
                kw = { f+'__icontains' : lookup['q'] }
                q = q | Q(**kw)
            queries.append(Q(keywords__word__icontains = lookup['q']))
        
        if 'title' in lookup.keys():
            # word-in-title is useful constraint
             q = q | Q(title__icontains = lookup['title'])        
        
        # if this search will come to apply to >1 object type, 
        # check at this stage that property is set..
        queries.append(q)    
        results = []
        for query in queries:
            # the "simple query" may be null (e.g. we only want time range or contacts)
            # in which case, don't run it.
            # QOr doesnt define kwargs method :(
            try: 
                if len(query.kwargs) == 0:
                    continue
            except:
                pass
                # we got here, go ahead.
            query_set = self.model.objects.filter(query)
            #    print query_set._get_sql_clause()
            for i in list(query_set):
                if i not in results:
                    results.append(i)

        return results

    def url_for_result(self, result):
        return settings.WWW_BASEURL+"dataset/%s/" % getattr(result, self.pk_attname)
 
class IncorrectLookupParameters(Exception):
    pass

